package com.handydata.antifraud.rule.action;

import com.bstek.urule.model.flow.ActionNode;
import com.bstek.urule.model.flow.FlowAction;
import com.bstek.urule.model.flow.ins.FlowContext;
import com.bstek.urule.model.flow.ins.ProcessInstance;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import com.handydata.antifraud.common.constant.AlarmInfoCode;
import com.handydata.antifraud.rule.RuleEngineVarCollector;
import com.handydata.antifraud.service.AlarmInfoService;
import com.handydata.antifraud.to.*;
import com.handydata.antifraud.util.ExceptionUtil;
import com.handydata.antifraud.ws.WsInvocationService;
import com.handydata.antifraud.ws.WsServiceFactory;
import com.handydata.common.convert.CommonBeanConvertor;

import lombok.extern.slf4j.Slf4j;

import org.apache.commons.lang3.tuple.MutablePair;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Component;

import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.*;

/**
 * 决策流动作执行类，执行webservice调用
 *
 * @author guolong
 * @version $Id: WsInvocationAction.java, v 0.1 2019年8月20日 上午10:21:43 apple Exp $
 */
@Slf4j
@Component
public class WsInvocationAction implements FlowAction {
    @Autowired
    private WsServiceFactory wsServiceFactory;


    //接口调用超时时间，单位：秒
    @Value("${ws.invocation.timeout:5}")
    private int invokeTimeout;

    @Autowired
    protected ThreadPoolTaskExecutor threadPoolTaskExecutor;

    @Autowired
    private AlarmInfoService alarmInfoService;

    @Override
    public void execute(ActionNode node, FlowContext context, ProcessInstance instance) {

        RiskInputTO riskInputTO = RuleEngineVarCollector.collect(context, RiskInputTO.class).get();
        RiskMiddleTO riskMiddleTO = RuleEngineVarCollector.collect(context, RiskMiddleTO.class).get();
        RiskOutputTO riskOutputTO = RuleEngineVarCollector.collect(context, RiskOutputTO.class).get();

        String wsServiceBeanName = node.getName();

        invokeWsService(riskInputTO,riskMiddleTO, riskOutputTO, wsServiceBeanName);
    }

    /**
     * 调用ws服务
     * @param riskInputTO
     * @param wsServiceBeanName
     */
    private void invokeWsService(RiskInputTO riskInputTO, RiskMiddleTO riskMiddleTO, RiskOutputTO riskOutputTO, String wsServiceBeanName) {

        String attr = "";
        //用于支持同一个接口查询多个相同类型的属性，如:手机号,联系人手机号
        if(wsServiceBeanName.contains("-")) {
            String[] splits = wsServiceBeanName.split("-");
            wsServiceBeanName = splits[0];
            attr = splits[1];
        }
        String beanName=wsServiceFactory.getBeanName(wsServiceBeanName);
        //alarmInfoService.startWatchTimeOut(AlarmInfoCode.ALERT_ITEM_WS.getCode(),riskInputTO.getOrderId(),beanName);

        MutablePair<WsInvocationService<WsReqTO, WsResTO>, Class<?>> pair = wsServiceFactory.getWsService(wsServiceBeanName);

        if(Objects.nonNull(pair)) {
            WsReqTO reqTO = (WsReqTO)CommonBeanConvertor.convert(riskInputTO, pair.right);
            reqTO.setAttr(attr);

            CompletableFuture<Optional<WsResTO>> future=null;
            try {
                log.info("开始发起调用数据源接口信息:{}",reqTO.toString());
                //这里的线程数多少呢
                future = CompletableFuture.supplyAsync(() -> ExceptionUtil.of(() -> pair.left.query(reqTO), ex -> handleException(ex,pair,riskInputTO.getOrderId(),beanName)), threadPoolTaskExecutor);
                Optional<WsResTO> optionalWsResTO = future.get(invokeTimeout, TimeUnit.SECONDS);
                if(optionalWsResTO.isPresent()) {
                    //resTO 拷贝到结果变量中
                    CommonBeanConvertor.convert(optionalWsResTO.get(), riskInputTO);
                    CommonBeanConvertor.convert(optionalWsResTO.get(), riskMiddleTO);
                    CommonBeanConvertor.convert(optionalWsResTO.get(), riskOutputTO);
                    riskOutputTO.setValue1(String.valueOf(riskInputTO.getAntiFraudScore()));
                    riskOutputTO.setValue2(String.valueOf(riskInputTO.getHlzBin13Score()));
                    riskOutputTO.setValue3(String.valueOf(riskInputTO.getAfMidscore()));
                    riskOutputTO.setValue4(String.valueOf(riskInputTO.getXysSmallMultiScore()));
                    riskOutputTO.setValue5(String.valueOf(riskInputTO.getTxFraudRiskScore()));
                    riskOutputTO.setValue6(String.valueOf(riskInputTO.getHdCreditscore()));
                    riskOutputTO.setValue7(String.valueOf(riskInputTO.getHdDt01005()));
                    riskOutputTO.setValue8(String.valueOf(riskInputTO.getHdDt01002()));
                }
            }catch (TimeoutException ex){
                log.error("调用外部数据源超时{}",wsServiceBeanName);
                future.cancel(true);
                alarmInfoService.sendWatchAbNormal(AlarmInfoCode.ALERT_ITEM_WS.getCode(),riskInputTO.getOrderId(),beanName,AlarmInfoCode.ALARM_TYPE_TIMEOUT.getCode(),
                		"执行外部数据出错:" + reqTO.getQueryXml().substring(reqTO.getQueryXml().indexOf("<channelOrderId>"), reqTO.getQueryXml().indexOf("</channelOrderId>")),ex);
            }
            catch(Exception e) {
                handleException(e,pair,riskInputTO.getOrderId(),beanName);
            }
        } else {
            handleException(new RuntimeException("未找到接口调用类:" + wsServiceBeanName),pair,riskInputTO.getOrderId(),beanName);
        }
    }
    /**
     * 处理异常情况
     */
    private void handleException(Exception e,MutablePair<WsInvocationService<WsReqTO, WsResTO>, Class<?>> pair,String orderId,String beanName) {
        log.error("执行动作失败{} Error:{} bean {}",pair.left,e,beanName);
        alarmInfoService.sendWatchAbNormal(AlarmInfoCode.ALERT_ITEM_WS.getCode(),orderId,beanName,
                AlarmInfoCode.ALARM_TYPE_TIMEOUT.getCode(),"执行计算出错",e);

    }
    @SuppressWarnings("unused")
	private ExecutorService newThreadPool(int corePoolSize, int maximumPoolSize, int capacity, ThreadFactory threadFactory) {
        return new ThreadPoolExecutor(corePoolSize, maximumPoolSize,
                60L, TimeUnit.SECONDS,
                new LinkedBlockingQueue<>(capacity), threadFactory);
    }
    @SuppressWarnings("unused")
	private ThreadFactory getNamedThreadFactory(String name) {
        return new ThreadFactoryBuilder()
                .setNameFormat(name + "risk-ws-queue-thread-%d").build();
    }
}